﻿//  Copyright (c) 2011 Ray Liang (http://www.dotnetage.com)
//  Licensed MIT: http://www.opensource.org/licenses/mit-license.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.IO;

namespace DNA.Mvc.Publishing
{
    public class PubService : IPubService
    {
        private IPubContext innerContext;
        private IDataContext dataContext;

        public PubService(IPubContext pubContext, IDataContext dbContext)
        {
            innerContext = pubContext;
            dataContext = dbContext;
        }

        #region Category

        public Category FindCategory(int id)
        {
            return innerContext.Categories.Find(id);
        }

        public Category FindCategory(string path)
        {
            return innerContext.Categories.Find(path);
        }

        public void DeleteCategory(int id)
        {
            var cat = innerContext.Categories.Find(id);
            if (cat != null)
            {
                innerContext.Categories.Delete(cat);
                innerContext.SaveChanges();
            }
        }

        public Category UpdateCategory(Category category)
        {
            if (category.ID == 0)
                innerContext.Categories.Create(category);
            else
                innerContext.Categories.Update(category);
            innerContext.SaveChanges();
            return category;
        }

        public IEnumerable<Category> GetCategories(int[] ids)
        {
            return innerContext.Categories.Includes(ids);
        }

        public IEnumerable<Category> GetChildrenCategories(int id)
        {
            return innerContext.Categories.Children(id);
        }

        public IEnumerable<Category> GetSuggestCategories(string prefix, int returns)
        {
            return innerContext.Categories.Filter(c => c.Title.Contains(prefix)).Distinct().Take(returns);
        }

        public IEnumerable<Category> GetCategories(string website)
        {
            if (string.IsNullOrEmpty(website))
                throw new ArgumentNullException("website");
            return innerContext.Categories.Filter(c => c.Path.StartsWith(website)).ToList();
        }

        public IEnumerable<Category> GetPostCategories(int articleID)
        {
            var article = GetPost(articleID);
            var categories = new List<Category>();
            categories.Add(article.Category);
            var topPath = article.Category.Path.Split('/')[0];

            if (!string.IsNullOrEmpty(article.Categories))
            {
                var catNames = article.Categories.Split(',');
                var local = GetCategories(topPath);
                foreach (var cname in catNames)
                {
                    var cat = local.FirstOrDefault(c => c.Title.Equals(cname));
                    if (cat != null)
                        categories.Add(cat);
                }
            }
            return categories;
        }

        #endregion

        #region Posts

        public Article NewPost(Article article, bool isAdmin = false)
        {
            var primaryCategory = article.Category == null ? FindCategory(article.CategoryID) : article.Category;

            if (primaryCategory == null)
                throw new Exception("Primary category could not be null.");

            if (primaryCategory.IsModerated)
            {
                if (article.IsPublished && isAdmin)
                    article.IsAppoved = false;
            }
            else
                article.IsAppoved = true;

            article.Pos = innerContext.Articles.GetPosition(primaryCategory.ID);
            if (string.IsNullOrEmpty(article.Slug))
                article.Slug = TextUtility.Slug(article.Title);

            article.Posted = DateTime.Now;
            article.Version = 0;
            article.Ratings = 0;
            article.TotalReads = 0;
            article.LastModified = DateTime.Now;

            innerContext.Articles.Create(article);
            innerContext.SaveChanges(false);
            primaryCategory.TotalPosts = innerContext.Categories.GetDescendantArticleCount(primaryCategory.ID);
            innerContext.Categories.Update(primaryCategory);
            innerContext.SaveChanges(false);
            string webName = primaryCategory.GetRootPath();

            if (string.IsNullOrEmpty(article.PermaLink))
            {
                string formattedTitle = article.Slug;
                string dateStr = article.Posted.ToString("yyyy/MM/dd");
                article.PermaLink = "/" + (string.IsNullOrEmpty(webName) ? "home" : webName) + "/" + dateStr + "/" + article.ID.ToString() + "/" + formattedTitle + ".html";
            }
            innerContext.SaveChanges(false);
            new ArticleUpdateEvent(article, true).Raise();

            return article;
        }

        public Article NewPost(string website, Article article, string[] categoryNames)
        {
            var web = dataContext.Webs.GetByName(website);

            if (string.IsNullOrEmpty(article.Language))
                article.Language = web.DefaultLanguage;

            if (categoryNames == null)
                return this.NewPost(article);

            var cats = GetCategories(website);
            if ((cats != null) && (cats.Count() > 0))
            {
                var _categories = cats.Where(c => categoryNames.Contains(c.Title));

                if ((_categories.Count() > 0) && (_categories != null))
                {
                    var _cats = _categories.ToList();
                    var primaryCategory = _cats[0];
                    if (_cats.Count > 1)
                        article.Categories = String.Join(",", _cats.Where(c => c.ID != primaryCategory.ID).Select(c => c.ID).ToArray());
                    article.Category = primaryCategory;
                }
                else
                    article.Category = FindCategory(website);
            }

            return NewPost(article);
        }

        public void EditPost(Article article)
        {
            article.LastModified = DateTime.Now;
            
            if (article.Category == null)
            {
                if (article.CategoryID > 0)
                    article.Category = FindCategory(article.CategoryID);
                else
                    throw new Exception("Category not set.");
            }

            string webName = article.Category.GetRootPath();
            if (string.IsNullOrEmpty(article.PermaLink))
            {
                string formattedTitle = article.Slug;
                string dateStr = article.Posted.ToString("yyyy/MM/dd");
                article.PermaLink = "/" + (string.IsNullOrEmpty(webName) ? "home" : webName) + "/" + dateStr + "/" + article.ID.ToString() + "/" + formattedTitle + ".html";
            }

            if (string.IsNullOrEmpty(article.Slug))
                article.Slug = TextUtility.Slug(article.Title);

            //Update states
            article.TotalComments = article.Comments.Count(c => c.IsApproved);
            article.TotalReads = article.Tracks.Count;
            article.TotalVotes = article.Votes.Count;

            if (article.Votes.Count() > 0)
                article.Ratings = article.Votes.Average(v => v.Rating);


            if (article.Category.EnableVersioning)
            {
                article.Version++;
                History version = new History()
                {
                    Version = article.Version,
                    Body = article.Body,
                    LastModified = DateTime.Now,
                    Modified = article.UserName,
                    Article = article
                };
                innerContext.Versions.Create(version);
            }

            innerContext.Articles.Update(article);
            innerContext.SaveChanges(false);

            new ArticleUpdateEvent(article).Raise();
        }

        public void MovePost(int id, int pos, int categoryID)
        {
            innerContext.Articles.Move(id, pos, categoryID);
            innerContext.SaveChanges(false);
        }

        public void DeletePost(int id)
        {
            var post = innerContext.Articles.Find(id);
            if (post != null)
            {
                //post.Category.TotalPosts--;
                post.Category.TotalPosts = innerContext.Categories.GetDescendantArticleCount(post.CategoryID);
                innerContext.Categories.Update(post.Category);
                innerContext.Delete(post);
                innerContext.SaveChanges();
            }
        }

        public void ReadPost(int id, string username, string ip, string userAgent)
        {
            var article = innerContext.Articles.Read(id, username, ip, userAgent);
            var rows = innerContext.SaveChanges();

            if (rows > 0)
            {
                article.TotalReads = article.Tracks.Count;
                innerContext.SaveChanges(false);
            }
        }

        public double Vote(int id, int value, string userName, string ip, out int total)
        {
            var article = innerContext.Articles.Vote(id, value, userName, ip);
            innerContext.SaveChanges();

            article.Ratings = article.Votes.Average(v => v.Rating);
            article.TotalVotes = article.Votes.Count;
            innerContext.SaveChanges(false);

            total = article.TotalVotes;
            return article.Ratings;
        }

        public Article GetPost(int id)
        {
            return innerContext.Articles.Find(id);
        }
        
        public IEnumerable<Article> GetPosts(int[] ids)
        {
            return innerContext.Articles.Includes(ids);
        }

        public Article GetPrevSiblingArticle(Article article)
        {
            if ((article != null) && (article.Category != null))
                return article.Category.Articles.OrderBy(a => a.Pos).FirstOrDefault(a => a.Pos < article.Pos && !a.ID.Equals(article.ID));
            //var articles = article.Category.Articles;
            //if (articles != null)
            //{
            //    var arts = articles.OrderBy(a => a.Pos).ToList();
            //    for (int i = 0; i < arts.Count; i++)
            //    {
            //        if (arts[i].ID == article.ID)
            //        {
            //            if ((i - 1) >= 0)
            //            {
            //                return arts[i - 1];
            //            }
            //        }
            //    }
            //}
            return null;
        }

        public Article GetNextSiblingArticle(Article article)
        {
            if ((article != null) && (article.Category != null))
                return article.Category.Articles.OrderBy(a => a.Pos).FirstOrDefault(a => a.Pos > article.Pos && !a.ID.Equals(article.ID));

            //var articles = article.Category.Articles;
            //if (articles != null)
            //{
            //    var arts = articles.OrderBy(a => a.Pos).ToList();
            //    for (int i = 0; i < arts.Count; i++)
            //    {
            //        if (arts[i].ID == article.ID)
            //        {
            //            if ((i + 1) < arts.Count)
            //            {
            //                return arts[i + 1];
            //            }
            //        }
            //    }
            //}
            return null;
        }

        public IQueryable<Article> GetDescendantArticles(int categoryId)
        {
            var cat = FindCategory(categoryId);
            var cats = GetCategories(cat.Path);
            var catIds = cats.Select(c => c.ID);
            return innerContext.Articles.All().Where(a => catIds.Contains(a.CategoryID));
        }

        public IEnumerable<Article> GetHotPosts(string website, int maxResults)
        {
            var cats = GetCategories(website);
            var catIds = cats.Select(c => c.ID);
            return innerContext.Articles.All().OrderByDescending(a => a.TotalReads).Where(a => catIds.Contains(a.CategoryID)).Take(maxResults);
        }

        public IEnumerable<Article> GetArchivePosts(string website, int year, int month)
        {
            var articles = GetDescendantArticles(FindCategory(website).ID);
            return articles.Where(a => ((a.Posted.Year == year) && (a.Posted.Month == month) && a.IsAppoved)).OrderByDescending(a => a.Posted);
        }

        public IEnumerable<Article> GetArchivePosts(string website, int year, int month, int pageIndex, int pageSize, out int totalRecords)
        {
            int skips = pageIndex * pageSize;
            var articles = GetDescendantArticles(FindCategory(website).ID);
            totalRecords = articles.Count(a => ((a.Posted.Year == year) && (a.Posted.Month == month) && a.IsAppoved));

            var query = articles.Where(a => ((a.Posted.Year == year) && (a.Posted.Month == month) && a.IsAppoved)).OrderByDescending(a => a.Posted);
            if (skips == 0)
                return query.Take(pageSize);
            else
                return query.Skip(skips).Take(pageSize);

        }

        public IEnumerable<Article> GetTagPosts(string website, string tagName, int pageIndex, int pageSize, out int totalRecords)
        {
            int skips = pageIndex * pageSize;
            var articles = GetDescendantArticles(FindCategory(website).ID);
            totalRecords = articles.Count(a => a.IsAppoved && a.IsPublished && a.Tags.Contains(tagName));
            var query = articles.Where(a => a.IsAppoved && a.IsPublished && a.Tags.Contains(tagName)).OrderByDescending(a => a.Posted);

            if (skips == 0)
                return query.Take(pageSize);
            else
                return query.Skip(skips).Take(pageSize);
        }

        public IEnumerable<Article> GetUnapprovePosts(string website, int pageIndex, int pageSize, out int totalRecords)
        {
            return innerContext.Articles.Filter(a => a.Posted, a => (a.Path.StartsWith(website) && !a.IsAppoved), out totalRecords, SortingOrders.Desc, pageIndex, pageSize).ToList();
        }

        public IEnumerable<Article> GetUserUnapprovePosts(string username, int pageIndex, int pageSize, out int totalRecords)
        {
            return innerContext.Articles.Filter(a => a.Posted, a => (a.UserName.Equals(username) && !a.IsAppoved && !a.IsPrivate), out totalRecords, SortingOrders.Desc, pageIndex, pageSize).ToList();
        }

        public IEnumerable<Article> GetDrafts(string website)
        {
            return innerContext.Articles.Filter(a => a.Path.StartsWith(website) && !a.IsPublished).ToList();
        }

        public IEnumerable<Article> GetDrafts(string website, int pageIndex, int pageSize, out int totalRecords)
        {
            return innerContext.Articles.Filter(a => a.Posted, a => (a.Path.StartsWith(website) && !a.IsPublished), out totalRecords, SortingOrders.Desc, pageIndex, pageSize).ToList();
        }

        public IEnumerable<Article> GetCategoryPosts(int categoryID, int pageIndex, int pageSize, out int totalRecords)
        {
            //var cats = GetChildrenCategories(categoryID);
            var cat = FindCategory(categoryID);
            //var ids = cats.Select(c => c.ID);
            var skips = pageIndex * pageSize;

            totalRecords = cat.Articles.Count;
            var query = cat.Articles.OrderByDescending(a => a.Pos);

            if (skips == 0)
                return query.Take(pageSize);
            else
                return query.Skip(skips).Take(pageSize);
        }

        public IEnumerable<Article> GetRecentPosts(int pageIndex, int pageSize, out int totalRecords)
        {
            return innerContext.Articles.Filter(a => a.Posted, a => (a.IsPublished && a.IsAppoved), out totalRecords, SortingOrders.Desc, pageIndex, pageSize).ToList();
        }

        public IEnumerable<Article> GetRecentPosts(string website, int pageIndex, int pageSize, out int totalRecords)
        {
            var cats = GetCategories(website);
            var keys = cats.Select(c => c.ID).ToArray();
            return innerContext.Articles.Filter(a => a.Posted, a => (keys.Contains(a.CategoryID) && a.IsPublished && a.IsAppoved), out totalRecords, SortingOrders.Desc, pageIndex, pageSize).ToList();
        }
        #endregion

        #region Comment

        public Comment AddComment(Comment comment)
        {
            innerContext.Comments.Create(comment);
            innerContext.SaveChanges(false);

            var article = comment.Article;
            article.TotalComments = article.Comments.Count(c => c.IsApproved);

            innerContext.SaveChanges(false);

            return comment;
        }

        public void DeleteComment(int id)
        {
            var cmm = innerContext.Comments.Find(id);
            if (cmm != null)
            {
                if (cmm.IsApproved)
                    cmm.Article.TotalComments--;

                innerContext.Comments.Delete(cmm);
                innerContext.SaveChanges(false);
            }
        }

        public IEnumerable<Comment> GetRecentComments(string website, int maxResults)
        {
            var cats = GetCategories(website);
            var ids = cats.Select(c => c.ID).ToArray();
            var articleIDs = innerContext.Articles.Filter(a => ids.Contains(a.ParentID)).Select(a => a.ID).ToArray();
            var query = innerContext.Comments.Filter(c => articleIDs.Contains(c.ArticleID)).OrderByDescending(c => c.Posted);
            if (maxResults <= 0)
                return query;
            else
                return query.Take(maxResults);
        }

        public IEnumerable<Comment> GetUnapproveComments(string id)
        {
            return innerContext.Articles.Find(id).Comments.Where(c => !c.IsApproved);
        }

        #endregion

        #region Blogs

        public IEnumerable<Blog> GetUserBlogs(string userName)
        {
            var webs = dataContext.Webs.Filter(w => w.Owner.Equals(userName));
            return GetBlogs(webs);
        }

        private IEnumerable<Blog> GetBlogs(IEnumerable<Web> webs)
        {
            var blogs = new List<Blog>();

            foreach (var web in webs)
            {
                var blog = new Blog()
                {
                    Name = web.Name,
                    Owner = web.Owner,
                    Title = web.Title,
                    Web = web,
                    Url = web.IsRoot ? UrlResolver.Resolve(web) : UrlResolver.Resolve(web) +"/index.html"
                };
                blogs.Add(blog);
            }
            return blogs;
        }

        public IEnumerable<Blog> GetBlogs()
        {
            var webs = dataContext.Webs.All();
            return GetBlogs(webs);
        }

        public IEnumerable<Blog> GetBlogs(int pageIndex, int pageSize, out int totalRecords)
        {
            var skips = pageIndex * pageSize;
            totalRecords = dataContext.Webs.Count();
            if (skips == 0)
                return GetBlogs(dataContext.Webs.All().Take(pageSize));
            else
                return GetBlogs(dataContext.Webs.All().Skip(skips).Take(pageSize));
        }

        #endregion

        public IEnumerable<Archive> GetArchives(string website)
        {
            return innerContext.Categories.GetArchives(website);
        }

        public IEnumerable<Tag> GetTags(string website, out int count)
        {
            return innerContext.Categories.GetTags(website, out count);
        }

        public string SaveMedia(string website, string filename, byte[] data, string contentType)
        {
            var web = dataContext.Webs.Find(website);
            var path = "~/shared/";

            if (web.IsRoot)
                path = path + "public/publishing/images/";
            else
                path = path + "personal/" + web.Name + "/publishing/images/";

            var phycalPath = HttpContext.Current.Server.MapPath(path);

            if (!Directory.Exists(phycalPath))
                Directory.CreateDirectory(phycalPath);

            File.WriteAllBytes(phycalPath + filename, data);
            return UrlResolver.ApplicationPath + "/webshared/" + web.Name + "/publishing/images/" + filename;
        }

        public TranslatedCopy Translate(TranslatedCopy copy)
        {
            if (copy.ID > 0)
                innerContext.TranslatedCopies.Update(copy);
            else
                innerContext.TranslatedCopies.Create(copy);
            innerContext.SaveChanges();
            return copy;
        }

        //public bool IsSpammer(string ip, string url)
        //{
        //    if (!string.IsNullOrEmpty(url))
        //    {
        //        if (innerContext.Spams.Count(s => s.IP.Equals(ip) || url.StartsWith(s.Url)) > 0)
        //            return true;
        //    }
        //    else
        //        if (innerContext.Spams.Count(s => s.IP.Equals(ip)) > 0)
        //            return true;
        //    return false;
        //}

        //public bool IsSpammer(HttpContextBase httpContext)
        //{
        //    var ip = httpContext.Request.UserHostAddress;
        //    var url = httpContext.Request.UrlReferrer != null ? httpContext.Request.UrlReferrer.ToString() : "";
        //    return IsSpammer(ip, url);
        //}

        public Comment FindComment(int id)
        {
            return innerContext.Comments.Find(id);
        }

        //public Spam MarkSpammer(Spam spammer)
        //{
        //    string url = string.IsNullOrEmpty(spammer.Url) ? "" : spammer.Url.ToLower();
        //    string ip = spammer.IP.ToLower();

        //    var _s = innerContext.Spams.Find(s => s.Url.Equals(url) && s.IP.Equals(ip));

        //    if (_s != null)
        //        return _s;

        //    var rubbishs = string.IsNullOrEmpty(url) ? innerContext.Comments.Filter(c => c.IP.Equals(ip)) : innerContext.Comments.Filter(c => c.IP.Equals(ip) || c.UrlReferrer.Contains(url));

        //    foreach (var rubbish in rubbishs)
        //        innerContext.Comments.Delete(rubbish);

        //    innerContext.Spams.Create(spammer);
        //    innerContext.SaveChanges();

        //    return spammer;
        //}

        //public IQueryable<Spam> GetSpammers()
        //{
        //    return innerContext.Spams.All();
        //}
    }
}
